﻿#region File and License Information
/*
<File>
	<License Type="BSD">
		Copyright © 2009 - 2012, Outcoder. All rights reserved.
	
		This file is part of Calcium (http://calciumsdk.net).

		Redistribution and use in source and binary forms, with or without
		modification, are permitted provided that the following conditions are met:
			* Redistributions of source code must retain the above copyright
			  notice, this list of conditions and the following disclaimer.
			* Redistributions in binary form must reproduce the above copyright
			  notice, this list of conditions and the following disclaimer in the
			  documentation and/or other materials provided with the distribution.
			* Neither the name of the <organization> nor the
			  names of its contributors may be used to endorse or promote products
			  derived from this software without specific prior written permission.

		THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
		ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
		WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
		DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
		DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
		(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
		LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
		ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
		(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
		SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	</License>
	<Owner Name="Daniel Vaughan" Email="danielvaughan@outcoder.com" />
	<CreationDate>2010-04-10 20:33:35Z</CreationDate>
</File>
*/
#endregion

using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;

namespace Outcoder.ComponentModel
{
	/// <summary>
	/// A base class for property change notification.
	/// <seealso cref="ComponentModel.PropertyChangeNotifier"/>.
	/// </summary>
	[Serializable]
	public abstract class ObservableObject : INotifyPropertyChanged, INotifyPropertyChanging
	{
		[field: NonSerialized]
		PropertyChangeNotifier notifier;

		readonly bool useExtendedEventArgs = true;

		/// <summary>
		/// Gets the notifier. It is lazy loaded.
		/// </summary>
		/// <value>The notifier.</value>
		protected PropertyChangeNotifier PropertyChangeNotifier
		{
			get
			{
				/* We use lazy instantiation because hooking up the events 
				 * for many instances is expensive. */
				if (notifier == null)
				{
					InitializePropertyChangeNotifier();
				}
				return notifier;
			}
		}

#if !NETFX_CORE
		[MethodImpl(MethodImplOptions.Synchronized)]
#endif
		void InitializePropertyChangeNotifier()
		{
			if (notifier == null)
			{
				notifier = new PropertyChangeNotifier(this, useExtendedEventArgs);
			}
		}

		/// <summary>
		/// Raises the PropertyChanged event.
		/// </summary>
		/// <param name="propertyName">Name of the property.</param>
		protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "")
		{
			PropertyChangeNotifier.NotifyChanged(propertyName);
		}

		/// <summary>
		/// Raises the PropertyChanged event.
		/// </summary>
		/// <param name="propertyExpression">An expression to retrieve the property. E.g., () => Property </param>
		/// <example>OnPropertyChanged(() => Name);</example>
		protected virtual void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> propertyExpression)
		{
			PropertyChangeNotifier.NotifyChanged(propertyExpression);
		}

//		[OnDeserializing]
//		void OnDeserializing(StreamingContext context)
//		{
//			Initialize();
//		}

//		/// <summary>
//		/// Assigns the specified newValue to the specified property
//		/// and then notifies listeners that the property has changed.
//		/// Assignment nor notification will occur if the specified
//		/// property and newValue are equal. 
//		/// Uses an <see cref="Expression"/> to determine the property name, 
//		/// which is slower than using the string property name overload.
//		/// </summary>
//		/// <typeparam name="T"></typeparam>
//		/// <typeparam name="TProperty">The type of the property.</typeparam>
//		/// <param name="expression">The expression that is used to derive the property name.
//		/// Should not be <code>null</code>.</param>
//		/// <param name="field">A reference to the field that is to be assigned.</param>
//		/// <param name="newValue">The value to assign the property.</param>
//		/// <exception cref="ArgumentNullException">
//		/// Occurs if the specified propertyName is <code>null</code>.</exception>
//		/// <exception cref="ArgumentException">
//		/// Occurs if the specified propertyName is an empty string.</exception>
//		protected AssignmentResult Assign<TProperty>(
//			Expression<Func<TProperty>> expression,
//			ref TProperty field, TProperty newValue)
//		{
//			return Notifier.Assign(expression, ref field, newValue);
//		}

		/// <summary> 
		/// Assigns the specified newValue to the specified property
		/// and then notifies listeners that the property has changed.
		/// Assignment nor notification will occur if the specified
		/// property and newValue are equal. 
		/// Not recommended for frequent operations 
		/// as evaluation of the property expression is slow.
		/// </summary>
		/// <typeparam name="TProperty">The type of the property.</typeparam>
		/// <typeparam name="TField">The type of the field. 
		/// When assignment occurs, a downcast is applied.</typeparam>
		/// <param name="propertyExpression">The expression that is used to derive the property name.
		/// Should not be <code>null</code>.</param>
		/// <param name="field">A reference to the property that is to be assigned.</param>
		/// <param name="newValue">The value to assign the property.</param>
		/// <exception cref="ArgumentNullException">
		/// Occurs if the specified propertyName is <code>null</code>.</exception>
		/// <exception cref="ArgumentException">
		/// Occurs if the specified propertyName is an empty string.</exception>
		protected AssignmentResult Assign<TProperty, TField>(
			Expression<Func<TProperty>> propertyExpression, ref TField field, TField newValue)
			where TField : TProperty
		{
			return PropertyChangeNotifier.Assign<TProperty, TField>(propertyExpression, ref field, newValue);
		}

		/// <summary>
		/// Assigns the specified newValue to the specified property
		/// and then notifies listeners that the property has changed.
		/// </summary>
		/// <typeparam name="TProperty">The type of the property.</typeparam>
		/// <param name="propertyName">Name of the property. Can not be null.</param>
		/// <param name="field">A reference to the property that is to be assigned.</param>
		/// <param name="newValue">The value to assign the property.</param>
		/// <exception cref="ArgumentNullException">
		/// Occurs if the specified propertyName is <code>null</code>.</exception>
		/// <exception cref="ArgumentException">
		/// Occurs if the specified propertyName is an empty string.</exception>
		protected AssignmentResult Assign<TProperty>(
			string propertyName, ref TProperty field, TProperty newValue)
		{
			return PropertyChangeNotifier.Assign(propertyName, ref field, newValue);
		}

		/// <summary>
		/// Assigns the specified newValue to the specified property
		/// and then notifies listeners that the property has changed.
		/// </summary>
		/// <typeparam name="TField">The type of the field.</typeparam>
		/// <param name="propertyName">Name of the property. Can not be null.</param>
		/// <param name="property">A reference to the property that is to be assigned.</param>
		/// <param name="newValue">The value to assign the property.</param>
		/// <exception cref="ArgumentNullException">
		/// Occurs if the specified propertyName is <code>null</code>.</exception>
		/// <exception cref="ArgumentException">
		/// Occurs if the specified propertyName is an empty string.</exception>
		protected AssignmentResult Assign<TField>(
			ref TField property, TField newValue, [CallerMemberName]string propertyName = "")
		{
			return PropertyChangeNotifier.Assign<TField>(propertyName, ref property, newValue);
		}

		/// <summary>
		/// Assigns the specified newValue to the specified WeakReference field ref
		/// and then notifies listeners that the property has changed.
		/// Assignment nor notification will occur if the specified
		/// property and newValue are equal. 
		/// Uses an <see cref="Expression"/> to determine the property name, 
		/// which is slower than using the string property name overload.
		/// </summary>
		/// <typeparam name="TProperty">The type of the property.</typeparam>
		/// <param name="propertyName">The name of the property being changed.</param>
		/// <param name="fieldReference">A reference to the field <see cref="WeakReference"/> 
		/// that is to be assigned.</param>
		/// <param name="newValue">The value to assign the property.</param>
		/// <exception cref="ArgumentNullException">
		/// Occurs if the specified propertyName is <code>null</code>.</exception>
		/// <exception cref="ArgumentException">
		/// Occurs if the specified propertyName is an empty string.</exception>
		protected AssignmentResult Assign<TProperty>(
			string propertyName, ref WeakReference fieldReference, TProperty newValue)
			where TProperty : class
		{
			return PropertyChangeNotifier.Assign(propertyName, ref fieldReference, newValue);
		}

		/// <summary>
		/// When deserialization occurs fields are not instantiated,
		/// therefore we must instantiate the notifier.
		/// </summary>
//		[MethodImpl(MethodImplOptions.Synchronized)]
//		void Initialize()
//		{
//		}

		public ObservableObject()
		{
//			Initialize();
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ObservableObject"/> class.
		/// </summary>
		/// <param name="useExtendedEventArgs">if set to <c>true</c> 
		/// the PropertyChangeNotifier will use extended event args.</param>
		protected ObservableObject(bool useExtendedEventArgs)
		{
			this.useExtendedEventArgs = useExtendedEventArgs;
		}

		#region Property change notification

		/// <summary>
		/// Occurs when a property value changes.
		/// <seealso cref="ComponentModel.PropertyChangeNotifier"/>
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged
		{
			add
			{
				PropertyChangeNotifier.PropertyChanged += value;
			}
			remove
			{
				PropertyChangeNotifier.PropertyChanged -= value;
			}
		}

		/// <summary>
		/// Occurs when a property value is changing.
		/// <seealso cref="ComponentModel.PropertyChangeNotifier"/>
		/// </summary>
		public event PropertyChangingEventHandler PropertyChanging
		{
			add
			{
				PropertyChangeNotifier.PropertyChanging += value;
			}
			remove
			{
				PropertyChangeNotifier.PropertyChanging -= value;
			}
		}

		#endregion
	}
}
